/*
* Copyright (c) 2008 by Windward Studios, Inc. All rights reserved.
*
* This software is the confidential and proprietary information of
* Windward Studios ("Confidential Information").  You shall not
* disclose such Confidential Information and shall use it only in
* accordance with the terms of the license agreement you entered into
* with Windward Studios, Inc.
*/

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;

namespace Kailua.net.windward.controls
{
	/// <summary>
	/// A TreeView with the added functionality that it holds LinkTreeNodes which can have hotlinks in their text.
	/// </summary>
	public partial class LinkTreeView : TreeView
	{

		private Color activeLinkColor = Color.Red;
		private Color linkColor = Color.Blue;
		private Color visitedLinkColor = Color.Green;

		// the delete bitmap out at the end.
		private static readonly Bitmap deleteActive;
		private static readonly Bitmap deleteDisabled;
		private static readonly Rectangle rectDeleteBitmap;

		/// <summary>
		/// Called when the user clicks on a link in a node.
		/// </summary>
		public event EventHandler<LinkTreeNodeMouseClickEventArgs> ChunkMouseClick;

		private Cursor cursorOnEntry = Cursors.Default;
		private LinkTreeNode lastDrawUnderline;

		static LinkTreeView()
		{
			deleteActive = Resources.Resources.delete;
			deleteDisabled = Resources.Resources.deleteDisabled;
			rectDeleteBitmap = new Rectangle(0, 0, deleteActive.Width, deleteActive.Height);
		}

		/// <summary>
		/// Create the control.
		/// </summary>
		public LinkTreeView()
		{
			InitializeComponent();
		}

		/// <summary>
		/// Create the control.
		/// </summary>
		/// <param name="container">The container to place this in.</param>
		public LinkTreeView(IContainer container)
		{
			container.Add(this);
			InitializeComponent();
		}

		/// <summary>
		/// The color for the link the cursor is over.
		/// </summary>
		public Color ActiveLinkColor
		{
			get { return activeLinkColor; }
			set { activeLinkColor = value; }
		}

		/// <summary>
		/// The color for links that are not marked visited.
		/// </summary>
		public Color LinkColor
		{
			get { return linkColor; }
			set { linkColor = value; }
		}

		/// <summary>
		/// The color for links that are marked visited.
		/// </summary>
		public Color VisitedLinkColor
		{
			get { return visitedLinkColor; }
			set { visitedLinkColor = value; }
		}

		private const int WM_ERASEBKGND = 0x0014;
		/// <summary>
		/// Override the message processor to stop flickering.
		/// </summary>
		/// <param name="msg">The message to process. Only handle WM_ERASEBKGND</param>
		protected override void WndProc(ref Message msg)
		{
			if (msg.Msg == WM_ERASEBKGND)
				return;
			base.WndProc(ref msg);
		}

		private void LinkTreeView_DrawNode(object sender, DrawTreeNodeEventArgs ea)
		{

			LinkTreeView tree = (LinkTreeView)sender;
			LinkTreeNode node = ea.Node as LinkTreeNode;
			if (node == null)
				throw new InvalidCastException("TreeNode elements must be of type LinkTreeNode");

			// if there are no chunks or no text, it can go default
			if ((node.Chunks.Count == 0) || (node.NodeText.Length == 0))
			{
				ea.DrawDefault = true;
				return;
			}

			DrawNodeText(ea.Graphics, tree, node);
		}

		private void DrawNodeText(Graphics graphics, Control tree, LinkTreeNode node)
		{

			// fonts
			Font nodeFont = node.NodeFont ?? tree.Font;
			using (Font underFont = new Font(nodeFont, FontStyle.Underline))
			{
				using (
					StringFormat format =
						new StringFormat(StringFormatFlags.MeasureTrailingSpaces | StringFormat.GenericTypographic.FormatFlags))
				{
					using (Brush foregroundBrush = new SolidBrush(ForeColor))
					{
						using (Brush linkBrush = new SolidBrush(linkColor))
						{
							using (Brush visitedBrush = new SolidBrush(visitedLinkColor))
							{

								// get the actual bounding rect
								Rectangle rect = new Rectangle(node.Bounds.Location, node.Bounds.Size);
								SizeF sz = graphics.MeasureString(node.NodeText, nodeFont, Int32.MaxValue, format);
								rect.Width = Math.Max(rect.Width, (int) Math.Ceiling(sz.Width));

								using (Brush backGroundBrush = new SolidBrush(BackColor))
								{
									graphics.FillRectangle(backGroundBrush, rect);
								}

								// find the underline parts
								ArrayList arrCharRng = new ArrayList();
								List<Brush> arrBrush = new List<Brush>();
								int textOffset = 0;
								int chunkIndex = 0;
								LinkTreeNodeChunk chunkOn = node.Chunks[0];
								while (textOffset < node.NodeText.Length)
								{
									// regular text
									if ((chunkOn == null) || (textOffset < chunkOn.Start))
									{
										int len = chunkOn == null ? node.NodeText.Length - textOffset : chunkOn.Start - textOffset;
										arrCharRng.Add(new CharacterRange(textOffset, len));
										textOffset += len;
										arrBrush.Add(null);
									}
										// underline
									else
									{
										arrCharRng.Add(new CharacterRange(textOffset, chunkOn.Length));
										textOffset += chunkOn.Length;
										arrBrush.Add(chunkOn.Visited ? visitedBrush : linkBrush);
										chunkIndex++;
										chunkOn = chunkIndex >= node.Chunks.Count ? null : node.Chunks[chunkIndex];
									}
								}
								CharacterRange[] range = (CharacterRange[]) arrCharRng.ToArray(typeof (CharacterRange));
								format.SetMeasurableCharacterRanges(range);

								// draw the underlines
								Region[] regions = graphics.MeasureCharacterRanges(node.NodeText, nodeFont, rect, format);

								node.under = new Rectangle[arrBrush.Count];
								for (int index = 0; index < arrBrush.Count; index++)
								{
									if (index >= regions.Length)
										break;

									Rectangle rectRgn = Rectangle.Round(regions[index].GetBounds(graphics));
									rect.X = rectRgn.Left;
									string text = node.NodeText.Substring(range[index].First, range[index].Length);
									if (arrBrush[index] == null)
										graphics.DrawString(text, nodeFont, foregroundBrush, rect, format);
									else
									{
										node.under[index] = rectRgn;
										graphics.DrawString(text, underFont, arrBrush[index], rect, format);
									}
								}

								if (node.DeleteVisible)
								{
									Rectangle nodeRect = Rectangle.Round(regions[regions.Length - 1].GetBounds(graphics));
									nodeRect.Offset(nodeRect.Right + 4 - nodeRect.Left, 0);
									int size = Math.Min(rectDeleteBitmap.Height, nodeRect.Height);
									if (size < rectDeleteBitmap.Height)
										nodeRect.Offset(0, (rectDeleteBitmap.Height - size) / 2);
									nodeRect.Height = nodeRect.Width = size;
									node.deleteRect = nodeRect;

									graphics.DrawImage(node.DeleteEnabled ? deleteActive : deleteDisabled, node.deleteRect, rectDeleteBitmap,
									                   GraphicsUnit.Pixel);

									if (!node.DeleteEnabled)
										node.deleteRect = Rectangle.Empty;
								}
								else
									node.deleteRect = Rectangle.Empty;
							}
						}
					}
				}
			}
		}

		private void DrawUnderlines(LinkTreeNode node, bool cursorOverLink)
		{
			using (Graphics grph = CreateGraphics())
			{
				using (Pen activeLinkPen = new Pen(activeLinkColor))
				{
					using (Pen linkPen = new Pen(linkColor))
					{
						using (Pen visitedLinkPen = new Pen(visitedLinkColor))
						{
							Point pt = PointToClient(Cursor.Position);
							int index = 0;
							foreach (Rectangle rect in node.under)
							{
								if (rect.Width == 0)
									continue;
								if (index >= node.Chunks.Count)
									return;

								LinkTreeNodeChunk chunkOn = node.Chunks[index++];
								Pen pen = chunkOn.Visited ? visitedLinkPen : linkPen;
								if (cursorOverLink && rect.Contains(pt))
									pen = activeLinkPen;
								grph.DrawLine(pen, rect.Left + 1, rect.Bottom - 2, rect.Right + 2, rect.Bottom - 2);
							}
						}
					}
				}
			}
		}

		private LinkTreeNodeChunk GetChunkOn(LinkTreeNode node, Point location)
		{

			using (Graphics grph = CreateGraphics())
			{
				using (StringFormat format = new StringFormat(StringFormatFlags.MeasureTrailingSpaces | StringFormat.GenericTypographic.FormatFlags))
				{
					Font nodeFont = node.NodeFont ?? Font;

					// get the actual bounding rect
					Rectangle rect = new Rectangle(node.Bounds.Location, node.Bounds.Size);
					SizeF sz = grph.MeasureString(node.NodeText, nodeFont, Int32.MaxValue, format);
					rect.Width = Math.Max(rect.Width, (int)Math.Ceiling(sz.Width));
					
					ArrayList arrCharRng = new ArrayList();
					ArrayList arrChunks = new ArrayList();
					int textOffset = 0;
					int chunkIndex = 0;
					LinkTreeNodeChunk chunkOn = node.Chunks[0];
					while (textOffset < node.NodeText.Length)
					{
						// regular text
						if ((chunkOn == null) || (textOffset < chunkOn.Start))
						{
							int len = chunkOn == null ? node.NodeText.Length - textOffset : chunkOn.Start - textOffset;
							arrCharRng.Add(new CharacterRange(textOffset, len));
							textOffset += len;
							arrChunks.Add(null);
						}
							// underline
						else
						{
							arrCharRng.Add(new CharacterRange(textOffset, chunkOn.Length));
							textOffset += chunkOn.Length;
							chunkIndex++;
							arrChunks.Add(chunkOn);
							chunkOn = chunkIndex >= node.Chunks.Count ? null : node.Chunks[chunkIndex];
						}
					}
					CharacterRange[] range = (CharacterRange[]) arrCharRng.ToArray(typeof (CharacterRange));
					format.SetMeasurableCharacterRanges(range);

					// convert to regions
					Region[] regions = grph.MeasureCharacterRanges(node.NodeText, nodeFont, rect, format);

					for (int index = 0; index < regions.Length; index++ )
					{
						rect = Rectangle.Truncate(regions[index].GetBounds(grph));
						rect.Inflate(-1, -1);
						if (rect.Contains(location))
							return (LinkTreeNodeChunk)arrChunks[index];
					}

					// delete?
					Rectangle nodeRect = node.deleteRect;
					if (nodeRect.Contains(location))
						return new LinkTreeNodeChunk(node.NodeText.Length, 0, node.DeleteChunk);

					return null;
				}
			}
		}

		private void LinkTreeView_MouseEnter(object sender, EventArgs e)
		{
			cursorOnEntry = Cursor;
		}

		private void LinkTreeView_MouseLeave(object sender, EventArgs e)
		{
			Cursor = cursorOnEntry;
			// bugbug LinkTreeNode nodeOn = GetNodeAt(Cursor.Position) as LinkTreeNode;
			if (lastDrawUnderline != null)
			{
				DrawUnderlines(lastDrawUnderline, false);
				lastDrawUnderline = null;
			}
		}

		private void LinkTreeView_MouseMove(object sender, MouseEventArgs e)
		{
			LinkTreeNode nodeOn = GetNodeAt(e.Location) as LinkTreeNode;
			if ((nodeOn == null) || (nodeOn.Chunks.Count == 0))
			{
				Cursor = Cursors.Default;
				return;
			}
			LinkTreeNodeChunk chunk = GetChunkOn(nodeOn, e.Location);
			Cursor = chunk != null ? Cursors.Hand : Cursors.Default;

			if ((lastDrawUnderline != null) && (lastDrawUnderline != nodeOn))
				DrawUnderlines(lastDrawUnderline, false);
			DrawUnderlines(nodeOn, chunk != null);
			lastDrawUnderline = nodeOn;
		}

		private void LinkTreeView_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e)
		{
			EventHandler<LinkTreeNodeMouseClickEventArgs> handler = ChunkMouseClick;
			if (handler != null)
			{
				LinkTreeNode nodeOn = GetNodeAt(e.Location) as LinkTreeNode;
				if ((nodeOn != null) && (nodeOn.Chunks.Count != 0))
				{
					LinkTreeNodeChunk chunk = GetChunkOn(nodeOn, e.Location);
					if (chunk != null)
						handler(this, new LinkTreeNodeMouseClickEventArgs(e, chunk.Tag));
				}
			}
		}
	}
}